﻿
/*
        /// 生成zbstudio的lua插件的自动补全api代码，放到 %zbstudio%\api\lua(ubuntu : opt/zbstudio/api/lua )
        还需要编写一个编译器交互的文件放到 %zbstudio%\interpreters(ubuntu : opt/zbstudio/interpreters )
        如unityslua所示： 
        api = {"baselib", "unity"}  , 其中“unity”与api下的API文件同名这样才能实现关联自动补全才能成功，
        name = “unity3d” ，是解析器即乱运行时的名字，当配置好后重启zbstudio并在project中选择unity3d，此时可进行unity3d代码的自动补全了
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Xml;
namespace SLua
{
    public class  GenZBraneAPI
    { 
        static BindingFlags binding = BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase;
        static Dictionary<string, XmlNode> xmlNodeCache = null;
        static List<string> loadedModule = null;

        public static bool IsCompilling
        {
            get
            {
                if (EditorApplication.isCompiling)
                {
                    Debug.Log("Unity Editor is compiling, please wait.");
                }
                return EditorApplication.isCompiling;
            }
        }

        /// <summary>
        /// 生成zbstudio的lua插件的自动补全api代码，放到%zbstudio%/api/lua(ubuntu : opt/zbstudio/api/lua )
        /// </summary>
        [MenuItem("Lua/API/GenZBraneAPI")]
        public static void Gen()
        {

            if (IsCompilling)
            {
                Debug.Log("Unity is Compilling");
                return;
            }
            Debug.Log(" GenZBraneAPI starting ...");
            Start();
            NameSpace ns = new NameSpace();
            //sb.Append("return {");
            foreach (Type luaB in CustomExportAPI.CustomTypeList)
            {
                ns.ParseType(luaB.Namespace,luaB);
            }
            //sb.Append("}");
            StopAndSave(ns.GetZbraneString());

            Debug.Log(" GenZBraneAPI done ");
        }

        class NameSpace { 
            public string Name = null;
            public List<NameSpace> SubsNS = new List<NameSpace>();
            public List<Type> Types = new List<Type>();
            public void ParseType(string ns,Type clst)
            {
                if (string.IsNullOrEmpty(ns))//没有命名空间
                {
                    Types.Add(clst);
                }
                else if (!ns.Contains('.'))
                {
                    NameSpace sub = GetSubNamespace(ns);
                    sub.ParseType(null, clst);
                }
                else
                {
                    string[] namespaces = ns.Split('.');
                    NameSpace subns = GetSubNamespace(namespaces[0]);
                    string newns = "";
                    for (int i = 1; i < namespaces.Length; i++)
                    {
                        newns += namespaces[i];
                        if (i < namespaces.Length - 1)
                            newns += '.';
                    }
                    subns.ParseType(newns,clst);
                }
            }
            NameSpace GetSubNamespace(string sbns)
            { 
                foreach(NameSpace n in SubsNS)
                {
                    if (sbns.Equals(n.Name))
                        return n;
                }
                NameSpace rt = new NameSpace();
                rt.Name = sbns;
                SubsNS.Add(rt);
                return rt ;
            }

            public string GetZbraneString()
            {
                if (Types.Count < 1 && SubsNS.Count < 1)
                    return "";
                StringBuilder sbn = new StringBuilder();

                //start
                if (Name == null)
                {
                    sbn.Append("return { \r\n");
                }
                else
                {
                    sbn.AppendLine();
                    sbn.Append(Name + " = { \r\n");
                    sbn.Append("type = \"lib\",");
                    sbn.Append("\r\n");
                    sbn.Append(string.Format(" description = \"lib :{0}\",", Name));
                    sbn.Append("\r\n");

                    sbn.Append("childs = {\r\n");//start childs
                } 
                //each type
                foreach (Type tp in Types)
                {
                    GenType(sbn, tp);
                }
                //each sub namespcae
                foreach (NameSpace ns in SubsNS)
                {
                    string sub = ns.GetZbraneString();
                    sbn.Append(sub);
                }
                //end 
                if (Name == null)
                    sbn.Append("\r\n}\r\n");
                else
                {
                    sbn.Append("\r\n}\r\n");//end childs
                    sbn.Append("\r\n}, \r\n");
                }

                return sbn.ToString();
            }
        }

        //总开始
        public static void Start()
        { 
            xmlNodeCache = new Dictionary<string, XmlNode>();
            loadedModule = new List<string>();
        }
        //总结束
        public static void StopAndSave(string text)
        {
            System.IO.File.WriteAllText("D:\\Development\\ZeroBraneStudio.git\\api\\lua\\unity.lua", text); 
            xmlNodeCache = null;
            loadedModule = null;
        }

        private static void GenType(StringBuilder sb, Type type)
        {
            if (type.IsEnum)
            {
                GenEnum(sb,type);
            }
            else
            {
                GenClass(sb,type);
            }
        } 

        private static void GenEnum(StringBuilder sb, System.Type type)
        {
            sb.Append(type.Name + " = { \r\n");
            sb.Append("type = \"Enum\",");
            sb.Append("\r\n");
            sb.Append(" description = \"Enum\",");
            sb.Append("\r\n");

            FieldInfo[] fields = type.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static); 
            for (int i = 0; i < fields.Length; i++)
            {
                sb.Append("-- enum member : " + fields[i].Name +"\r\n");

                FieldInfo field = fields[i];
                sb.Append( field.Name + " = {\r\n"); 
                sb.Append("type = \"value\",");
                sb.Append("\r\n");
                string desc = "";
                object[] comment = field.GetCustomAttributes(true);
                if(comment != null && comment.Length > 0)
                {
                    string methodComment = comment[0] as string;
                    desc = FormatComment(methodComment);
                }
                sb.Append(string.Format(" description = \"{0}\",", desc));
                if(i < fields.Length -1)
                    sb.Append("\r\n},\r\n");
                else
                    sb.Append("\r\n}\r\n");
            }
            sb.Append("},\r\n");
        }


        private static void RemovePropertyFunction(MethodInfo[] methods, PropertyInfo[] ps, out MethodInfo[] outMethods)
        {
            List<MethodInfo> list = methods.ToList();

            for (int i = 0; i < ps.Length; i++)
            {

                int index = list.FindIndex((m) => { return m.Name == "get_" + ps[i].Name; });

                if (index >= 0 && list[index].Name != "get_Item")
                {
                    list.RemoveAt(index);
                }

                index = list.FindIndex((m) => { return m.Name == "set_" + ps[i].Name; });

                if (index >= 0 && list[index].Name != "set_Item")
                {
                    list.RemoveAt(index);
                }
            }

            outMethods = list.ToArray();
        }
        private static void GenClass(StringBuilder sb, Type type)
        {
            //start class
            sb.Append(type.Name.Replace("`","") + " = { \r\n");
            Dictionary<string, ArrayList> methodNames = new Dictionary<string, ArrayList>();
            sb.Append("childs = {\r\n");
            //propertys
            PropertyInfo[] ps = type.GetProperties();
            for (int i = 0; i < ps.Length; i++)
            {
                PropertyInfo p = ps[i];
                if (IsObsolete(p))
                    continue;
                //start 
                sb.Append(p.Name + " = { \r\n"); 
                sb.Append("type = \"value\",");
                sb.Append("\r\n");
                sb.Append("description = \"");  
                if (p.CanRead)
                    sb.Append("get\t");
                if (p.CanWrite)
                    sb.Append("set\t");
                 
                sb.Append(FormatComment(GetpropertyComment(p))); 

                sb.Append("\",\r\n");
                sb.Append("},\r\n");
                //end
            }


            //静态函数
            binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly;
            MethodInfo[] methods = type.GetMethods(binding);
            RemovePropertyFunction(methods, ps, out methods);
            for (int i = 0; i < methods.Length; i++)
            {
                MethodInfo method = methods[i];
                if (IsObsolete(method))
                    continue;
                if (method.IsGenericMethod)
                    continue;
                ArrayList thisNameMethods = null;
                if (methodNames.TryGetValue(method.Name, out thisNameMethods))
                {//这个函数有了
                    thisNameMethods.Add(method);
                }
                else
                {
                    ArrayList newArray = new ArrayList();
                    newArray.Add(method);
                    methodNames.Add(method.Name, newArray);
                }
            }

            //gen methods
            int methodIndex = 0;
            foreach (var item in methodNames)
            {
                ArrayList thisNameMethods = item.Value; 
                //start  
                MethodInfo m = thisNameMethods[0] as MethodInfo;
                sb.Append("-- method for " + m.Name + "\r\n");

                sb.Append(m.Name + " = { \r\n");
                sb.Append("type = \"method\",");
                sb.Append("\r\n");
                ParameterInfo[] param = m.GetParameters();
                if (m.GetParameters().Length > 0)
                {
                    sb.Append("args = \"(");
                    for (int i = 0; i < param.Length; i++)
                    {
                        sb.Append(param[i].Name + "<" + param[i].ParameterType + ">");
                        if (i < param.Length - 1)
                            sb.Append(",");
                    }
                    sb.Append(")\",");
                }
                else
                {
                    sb.Append("args = \"()\",");
                }
                sb.Append("\r\n");
                sb.Append("description = \" ");//start method description , descript for muilty override methods
                int methodNum = 0; 
                foreach (MethodInfo method in thisNameMethods)
                {
                     /*
                      * static void Func(int a,float b)
                                int Fun(int a,float b)*/
                    string mOverride = method.ReturnType.Name;
                    if (method.IsStatic)
                    {
                        mOverride += " " + type.Name + "." + method.Name + "(";
                    }
                    else
                    {
                        mOverride += " " + type.Name + ":" + method.Name + "(";
                    }
                    ParameterInfo[] args = method.GetParameters(); 
                    for (int i = 0; i < args.Length; i++)
                    {
                        ParameterInfo arg = args[i];
                        mOverride += arg.Name + "<"+ arg.ParameterType.Name + ">";
                        if (i < args.Length - 1)
                            mOverride += ",";
                    }
                    mOverride += ")\\n";
                    sb.Append("[" + (methodNum++) + "]    " + mOverride); 
                    sb.Append(",\\n");
                }
                
                sb.Append(FormatComment(GetMethodComment(m)));
                sb.Append("\",\r\n");//end method description

                sb.Append(" returns = \""+ m.ReturnType.Name+"\""); 
                if(methodIndex < methodNames.Count -1)//end method
                    sb.Append("\r\n},\r\n");
                else
                    sb.Append("\r\n}\r\n");
                
                methodIndex += 1;
            }

            sb.Append("},\r\n");//end childs

            //start class description
            sb.Append("description = \"" + type.FullName);
            sb.Append("\\n");

            sb.Append("Constructors : \\n ");
            //构造函数
            ConstructorInfo[] constructors = type.GetConstructors();
            if (constructors.Length > 0)
            {
                int index = 0;
                for (int i = 0; i < constructors.Length; i++)
                {
                    ConstructorInfo con = constructors[i];
                    if (IsObsolete(con))
                        continue;
                    sb.Append("[" + (index++) + "] : ");
                    sb.Append(type.Name + "(");
                    ParameterInfo[] args = con.GetParameters();
                    for (int j = 0; j < args.Length; j++)
                    {
                        sb.Append(args[j].ParameterType.Name + " " + args[j].Name);
                        if (j < args.Length - 1)
                            sb.Append(",");
                    }
                    sb.Append(")\\n");
                    string methodComment = FormatComment(GetMethodComment(con));
                    sb.Append(methodComment + "\\n");
                    sb.Append("\\n");
                }
            }
            sb.Append("\",\r\n");
            //end class description
            sb.Append("type = \"class\"");
            sb.Append("\r\n},\r\n");// end class
        }
        private static string FormatComment(string comment)
        {             
            if (comment != null && comment.Length > 0)
            { 
                comment = comment.Replace("\r\n", "^").Replace('\r', '^').Replace('\n', '^');
                return comment.Replace("^", "\\n").Replace("\"","");
            }
            return "";
        }
        private static string GetpropertyComment(PropertyInfo p)
        {
            string fullMethodNameInXml = "P:";
            fullMethodNameInXml += p.ReflectedType.FullName;
            fullMethodNameInXml += "." + p.Name;

            return GetCommentFromXml(p.Module, fullMethodNameInXml);

        }
        private static string GetMethodComment(MethodBase method)
        {
            string fullMethodNameInXml = "M:";
            fullMethodNameInXml += method.ReflectedType.FullName;
            if (method.IsConstructor)
                fullMethodNameInXml += ".#ctor";
            else
                fullMethodNameInXml += "." + method.Name;

            ParameterInfo[] args = method.GetParameters();
            if (args.Length > 0)
            {
                fullMethodNameInXml += "(";
                for (int i = 0; i < args.Length; i++)
                {
                    ParameterInfo arg = args[i];
                    fullMethodNameInXml += arg.ParameterType.FullName;
                    if (i < args.Length - 1)
                    {
                        fullMethodNameInXml += ",";
                    }
                }
                fullMethodNameInXml += ")";
            }



            return GetCommentFromXml(method.Module, fullMethodNameInXml);



        }

        private static string GetCommentFromXml(Module module, string fullMethodNameInXml)
        {
            string moduleName = module.Name;
            XmlNode xmlNode = null;

            bool moduleLoaded = false;
            foreach (var str in loadedModule)
            {
                if (str == moduleName)
                {
                    moduleLoaded = true;
                    break;
                }
            }

            if (!moduleLoaded)
            {
                loadedModule.Add(moduleName);
                string dllPath = module.FullyQualifiedName;
                string xmlPath = System.IO.Path.ChangeExtension(dllPath, "xml");
                XmlDocument doc = new XmlDocument();
                try
                {
                    doc.Load(xmlPath);
                    XmlNode root = doc.SelectSingleNode("doc");
                    root = root["members"];

                    for (int i = 0; i < root.ChildNodes.Count; i++)
                    {
                        XmlNode node = root.ChildNodes[i];
                        if (node.Name == "member" && node.Attributes["name"] != null)
                        {
                            XmlNode tmp = null;
                            if (!xmlNodeCache.TryGetValue(node.Attributes["name"].Value, out tmp))
                                xmlNodeCache.Add(node.Attributes["name"].Value, node);
                        }
                    }

                }
                catch (Exception e)
                {
                    return null;
                }

            }

            if (!xmlNodeCache.TryGetValue(fullMethodNameInXml, out xmlNode))
            {
                return null;
            }

            if (xmlNode == null)
                return null;



            //开始找咯



            //ok 找到这个node了 就要拿子节点了
            string ret = "";
            if (xmlNode["summary"] != null)
                if (xmlNode["summary"]["para"] != null)
                {
                    ret = xmlNode["summary"]["para"].InnerText;
                    ret = ret.Replace('\r', ' ');
                    ret = ret.Replace('\n', ' ');
                }

            //看看有没有参数信息 有就也列出来
            bool paramInfoPrinted = false;

            for (int j = 0; j < xmlNode.ChildNodes.Count; j++)
            {
                if (xmlNode.ChildNodes.Item(j).Name == "param")
                {


                    if (xmlNode.ChildNodes.Item(j).Attributes["name"] != null)
                    {
                        string tmp = xmlNode.ChildNodes.Item(j).InnerText;
                        if (tmp.Length > 0)
                        {
                            if (!paramInfoPrinted)
                            {
                                ret += "\r\n--params:\r\n";
                                paramInfoPrinted = true;
                            }
                            tmp = tmp.Replace('\r', ' ');
                            tmp = tmp.Replace('\n', ' ');
                            ret += "--" + xmlNode.ChildNodes.Item(j).Attributes["name"].Value + ":    " + tmp + "\r\n";
                        }

                    }
                }

            }

            ret = ret.Replace("[[", "{{");
            ret = ret.Replace("]]", "}}");

            return ret;








            return null;
        }
        private static bool IsObsolete(MemberInfo mb)
        {
            object[] attrs = mb.GetCustomAttributes(true);

            for (int j = 0; j < attrs.Length; j++)
            {
                Type t = attrs[j].GetType();

                if (t == typeof(System.ObsoleteAttribute))
                {
                    return true;
                }
            }
            return false;
        }                
    }
}
